package org.marlin.http;

import org.marlin.http.proxy.HttpFactoryBean;

import java.io.IOException;
import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.context.annotation.ClassPathBeanDefinitionScanner;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.core.type.filter.TypeFilter;
import org.springframework.util.StringUtils;

/**
 * init version 1.5.0
 * @author Jack ZG
 * @since 1.0.0
 */
public class ClassPathHttpScanner
        extends ClassPathBeanDefinitionScanner {
    private Class<? extends Annotation> annotationClass;
    private String[] httpHandlerBeanNames;
    private Map<String, RuntimeBeanReference> httpHandlers;
    private HttpFactoryBean<?> mapperFactoryBean;

    public ClassPathHttpScanner(BeanDefinitionRegistry registry) {
        super(registry, false);
    }


    public void setAnnotationClass(Class<? extends Annotation> annotationClass) {
        this.annotationClass = annotationClass;
    }

    public void setMapperFactoryBean(HttpFactoryBean<?> mapperFactoryBean) {
        this.mapperFactoryBean = (mapperFactoryBean != null) ? mapperFactoryBean : new HttpFactoryBean();
    }

    public void registerFilters() {
        if (this.annotationClass != null) {
            addIncludeFilter((TypeFilter) new AnnotationTypeFilter(this.annotationClass));
        }

        addExcludeFilter(new TypeFilter() {
            public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
                String className = metadataReader.getClassMetadata().getClassName();
                return className.endsWith("package-info");
            }
        });
    }


    public Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
        if (beanDefinitions.isEmpty()) {
            this.logger.warn("No HttpClient interface was found in '" + Arrays.toString((Object[]) basePackages) + "' package. Please check your org.marlin.cloud.configuration.");
        } else {
            processBeanDefinitions(beanDefinitions);
        }

        return beanDefinitions;
    }

    private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        Iterator<BeanDefinitionHolder> beanDefinitionHolderIterator = beanDefinitions.iterator();

        while (beanDefinitionHolderIterator.hasNext()) {
            BeanDefinitionHolder holder = beanDefinitionHolderIterator.next();
            GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
            if (this.logger.isDebugEnabled()) {
                this.logger.debug("Creating HttpFactoryBean with name '" + holder.getBeanName() + "' and '" + definition.getBeanClassName() + "' httpInterface");
            }

            definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
            definition.setBeanClass(this.mapperFactoryBean.getClass());
            for (int i = 0, size = this.httpHandlerBeanNames.length; i < size; i++) {
                if (StringUtils.hasText(this.httpHandlerBeanNames[i])) {
                    definition.getPropertyValues().add("httpHandler", new RuntimeBeanReference(this.httpHandlerBeanNames[i]));

                    definition.setAutowireMode(i + 1);
                }
            }
        }
    }


    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        return (beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent());
    }


    protected boolean checkCandidate(String beanName, BeanDefinition beanDefinition) {
        if (super.checkCandidate(beanName, beanDefinition)) {
            return true;
        }
        this.logger.warn("Skipping HttpFactoryBean with name '" + beanName + "' and '" + beanDefinition.getBeanClassName() + "' httpInterface. Bean already defined with the same name!");
        return false;
    }


    public String[] getHttpHandlerBeanNames() {
        return this.httpHandlerBeanNames;
    }

    public void setHttpHandlerBeanNames(String[] httpHandlerBeanNames) {
        this.httpHandlerBeanNames = httpHandlerBeanNames;
    }

    public Map<String, RuntimeBeanReference> getHttpHandlers() {
        return this.httpHandlers;
    }

    public void setHttpHandlers(Map<String, RuntimeBeanReference> httpHandlers) {
        this.httpHandlers = httpHandlers;
    }
}

